/*
 * Copyright (c) OSGi Alliance (2005). All Rights Reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this
 * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
 *
 * This copyright applies to the comments and public final static String fields only
 */
package org.osgi.core.framework
{
	import com.ericfeminella.collections.StringTokenizer;
	import com.winnemconsulting.javatoactionscript.lang.IComparable;

	import mx.utils.StringUtil;

	/**
	 * Version identifier for bundles and packages.
	 *
	 * <p>
	 * Version identifiers have four components.
	 * <ol>
	 * <li>Major version. A non-negative integer.</li>
	 * <li>Minor version. A non-negative integer.</li>
	 * <li>Micro version. A non-negative integer.</li>
	 * <li>Qualifier. A text string. See <code>Version(String)</code> for the
	 * format of the qualifier string.</li>
	 * </ol>
	 *
	 * <p>
	 * <code>Version</code> objects are immutable.
	 *
	 * @version $Revision: 1.16 $
	 * @since 1.3
	 */
	public class Version
	{
		private var major:int;
		private var minor:int;
		private var micro:int;
		private var qualifier:String;
		private static var SEPARATOR:String="."; //$NON-NLS-1$

		/**
		 * The empty version "0.0.0". Equivalent to calling
		 * <code>new Version(0,0,0)</code>.
		 */
		public static var emptyVersion:Version=new Version(0, 0, 0);

		/**
		 * Creates a version identifier from the specifed components.
		 *
		 * @param major Major component of the version identifier.
		 * @param minor Minor component of the version identifier.
		 * @param micro Micro component of the version identifier.
		 * @param qualifier Qualifier component of the version identifier. If
		 *        <code>null</code> is specified, then the qualifier will be set
		 *        to the empty string.
		 * @throws IllegalArgumentException If the numerical components are negative
		 *         or the qualifier string is invalid.
		 */
		public function Version(major:int=0, minor:int=0, micro:int=0, qualifier:String=null, version:String=null)
		{
			if (version != null)
				_version(version);

			if (qualifier == null)
			{
				qualifier=""; //$NON-NLS-1$
			}

			this.major=major;
			this.minor=minor;
			this.micro=micro;
			this.qualifier=qualifier;
			validate();
		}

		/**
		 * Created a version identifier from the specified string.
		 *
		 * <p>
		 * Here is the grammar for version strings.
		 *
		 * <pre>
		 * version ::= major('.'minor('.'micro('.'qualifier)?)?)?
		 * major ::= digit+
		 * minor ::= digit+
		 * micro ::= digit+
		 * qualifier ::= (alpha|digit|'_'|'-')+
		 * digit ::= [0..9]
		 * alpha ::= [a..zA..Z]
		 * </pre>
		 *
		 * There must be no whitespace in version.
		 *
		 * @param version String representation of the version identifier.
		 * @throws IllegalArgumentException If <code>version</code> is improperly
		 *         formatted.
		 */
		private function _version(version:String):void
		{
			var major:int=0;
			var minor:int=0;
			var micro:int=0;
			var qualifier:String=""; //$NON-NLS-1$

			try
			{
				var st:StringTokenizer=new StringTokenizer(version, SEPARATOR);
				major=parseInt(st.nextToken());

				if (st.hasMoreTokens())
				{
					st.nextToken(); // consume delimiter
					minor=parseInt(st.nextToken());

					if (st.hasMoreTokens())
					{
						st.nextToken(); // consume delimiter
						micro=parseInt(st.nextToken());

						if (st.hasMoreTokens())
						{
							st.nextToken(); // consume delimiter
							qualifier=st.nextToken();

							if (st.hasMoreTokens())
							{
								throw new ArgumentError("invalid format"); //$NON-NLS-1$
							}
						}
					}
				}
			}
			catch (error:Error)
			{
				throw new ArgumentError("invalid format"); //$NON-NLS-1$
			}

			this.major=major;
			this.minor=minor;
			this.micro=micro;
			this.qualifier=qualifier;
			validate();
		}

		/**
		 * Called by the Version constructors to validate the version components.
		 *
		 * @throws IllegalArgumentException If the numerical components are negative
		 *         or the qualifier string is invalid.
		 */
		private function validate():void
		{
			if (major < 0)
			{
				throw new ArgumentError("negative major"); //$NON-NLS-1$
			}
			if (minor < 0)
			{
				throw new ArgumentError("negative minor"); //$NON-NLS-1$
			}
			if (micro < 0)
			{
				throw new ArgumentError("negative micro"); //$NON-NLS-1$
			}
			var length:int=qualifier.length;
			for (var i:int=0; i < length; i++)
			{
				if ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-".indexOf(qualifier.charAt(i)) == -1)
				{ //$NON-NLS-1$
					throw new ArgumentError("invalid qualifier"); //$NON-NLS-1$
				}
			}
		}

		/**
		 * Parses a version identifier from the specified string.
		 *
		 * <p>
		 * See <code>Version(String)</code> for the format of the version string.
		 *
		 * @param version String representation of the version identifier. Leading
		 *        and trailing whitespace will be ignored.
		 * @return A <code>Version</code> object representing the version
		 *         identifier. If <code>version</code> is <code>null</code> or
		 *         the empty string then <code>emptyVersion</code> will be
		 *         returned.
		 * @throws IllegalArgumentException If <code>version</code> is improperly
		 *         formatted.
		 */
		public static function parseVersion(version:String):Version
		{
			if (version == null)
			{
				return emptyVersion;
			}

			version=StringUtil.trim(version);
			if (version.length == 0)
			{
				return emptyVersion;
			}

			return new Version(0, 0, 0, null, version);
		}

		/**
		 * Returns the major component of this version identifier.
		 *
		 * @return The major component.
		 */
		public function getMajor():int
		{
			return major;
		}

		/**
		 * Returns the minor component of this version identifier.
		 *
		 * @return The minor component.
		 */
		public function getMinor():int
		{
			return minor;
		}

		/**
		 * Returns the micro component of this version identifier.
		 *
		 * @return The micro component.
		 */
		public function getMicro():int
		{
			return micro;
		}

		/**
		 * Returns the qualifier component of this version identifier.
		 *
		 * @return The qualifier component.
		 */
		public function getQualifier():String
		{
			return qualifier;
		}

		/**
		 * Returns the string representation of this version identifier.
		 *
		 * <p>
		 * The format of the version string will be <code>major.minor.micro</code>
		 * if qualifier is the empty string or
		 * <code>major.minor.micro.qualifier</code> otherwise.
		 *
		 * @return The string representation of this version identifier.
		 */
		public function toString():String
		{
			var base:String=major + SEPARATOR + minor + SEPARATOR + micro;
			if (qualifier.length == 0)
			{ //$NON-NLS-1$
				return base;
			}
			else
			{
				return base + SEPARATOR + qualifier;
			}
		}

		/**
		 * Returns a hash code value for the object.
		 *
		 * @return An integer which is a hash code value for this object.
		 */
		public function hashCode():int
		{
			return (major << 24) + (minor << 16) + (micro << 8) + parseInt(qualifier, 32);
		}

		/**
		 * Compares this <code>Version</code> object to another object.
		 *
		 * <p>
		 * A version is considered to be <b>equal to </b> another version if the
		 * major, minor and micro components are equal and the qualifier component
		 * is equal (using <code>String.equals</code>).
		 *
		 * @param object The <code>Version</code> object to be compared.
		 * @return <code>true</code> if <code>object</code> is a
		 *         <code>Version</code> and is equal to this object;
		 *         <code>false</code> otherwise.
		 */
		public function equals(object:Object):Boolean
		{
			if (object == this)
			{
				return true;
			}

			if (!(object is Version))
			{
				return false;
			}

			var other:Version=Version(object);
			return (major == other.major) && (minor == other.minor) && (micro == other.micro) && (qualifier == other.qualifier);
		}

		/**
		 * Compares this <code>Version</code> object to another object.
		 *
		 * <p>
		 * A version is considered to be <b>less than </b> another version if its
		 * major component is less than the other version's major component, or the
		 * major components are equal and its minor component is less than the other
		 * version's minor component, or the major and minor components are equal
		 * and its micro component is less than the other version's micro component,
		 * or the major, minor and micro components are equal and it's qualifier
		 * component is less than the other version's qualifier component (using
		 * <code>String.compareTo</code>).
		 *
		 * <p>
		 * A version is considered to be <b>equal to</b> another version if the
		 * major, minor and micro components are equal and the qualifier component
		 * is equal (using <code>String.compareTo</code>).
		 *
		 * @param object The <code>Version</code> object to be compared.
		 * @return A negative integer, zero, or a positive integer if this object is
		 *         less than, equal to, or greater than the specified
		 *         <code>Version</code> object.
		 * @throws ClassCastException If the specified object is not a
		 *         <code>Version</code>.
		 */
		public function compareTo(object:Object):int
		{
			if (object == this)
			{
				return 0;
			}

			var other:Version=Version(object);

			var result:int=major - other.major;
			if (result != 0)
			{
				return result;
			}

			result=minor - other.minor;
			if (result != 0)
			{
				return result;
			}

			result=micro - other.micro;
			if (result != 0)
			{
				return result;
			}

			return Number(qualifier == other.qualifier);
		}
	}
}